<?php
namespace app\shop\library;

use cubox\Cubox;
use cubox\cache\Redis;
use cubox\provider\exception\LogException;
//最多不能超过53位

class Snowflake{
    private $workerId;
    private $redis;
    private $type;

    const STARTTIME = 1704038400; //32bit作为秒级时间戳
    /** 机器id所占位数 (支持最大机器数 2^5 = 32 )*/
    const WORKER_ID_BITS     = 5;
    /** 自增序列所占位数 (支持最大每秒生成 2^12 = 4096 ) */
    const SEQUENCE_BITS      = 12; // 毫秒内自增位
    

    private $maxWorkerId     = -1 ^ (-1 << self::WORKER_ID_BITS);
    private $workerIdShift   = self::SEQUENCE_BITS;
    private $timestampLeftShift = self::SEQUENCE_BITS + self::WORKER_ID_BITS;
    private $timestampMaxNum = -1 ^ (-1 << self::SEQUENCE_BITS);


    public function __construct(Redis $redis,$type, $workerId = 0)
    {
        if ($workerId > $this->maxWorkerId || $workerId < 0) {
            throw new \Exception('worker Id can\'t be greater than ' . $this->maxWorkerId . ' or less than 0');
        }

        $this->workerId = $workerId;
        $this->type = $type;
        $this->redis = $redis;
      
    }

    public function nextId()
    {

        if (!Lock::$isLocked){
            throw new \Exception('请先获取锁');
        }

        $seq = $this->getSequence();
        $max =  200;
        //$sleep_time = 1000000/100;
        while($seq  === false && $max > 0){
            //睡眠10毫秒
            //usleep($sleep_time);

            $max--;

            if ($max==0){
               throw new LogException('无法获取到正确的snowflake id');
            }

            $seq = $this->getSequence();
        }
    
        $offset_time = time()-self::STARTTIME;
        $combine = (($offset_time) << $this->timestampLeftShift) | ($this->workerId << $this->workerIdShift) | $seq;
        return $combine;
    }

    private function getSequence(){
        
        //$cache_nkey = 'snowflake_'.$this->type.'_'.time().'_nx';
        /*
        if ($this->redis->setnx($cache_nkey, 1)){
            $this->redis->expire($cache_nkey, 1);
            $sequence = $this->redis->incr($cache_key);
            if ($sequence==1)  $this->redis->expire($cache_key, 1);

            //删除锁
            $this->redis->del($cache_nkey);

            if ($sequence > $this->timestampMaxNum) {
                //睡眠到下一秒开始的时间
                list($microsecondsNow, $secondsNow) = explode(" ", microtime());
                $nextTime = 1000000 - (float)$microsecondsNow * 1000000;
                Cubox::log('snowflake_'.$this->type.'_'.time().' 内序列已用完 '.microtime(true));
                usleep($nextTime);
                return false;
            }else{
                return $sequence;
            }
        }else{
            return false;
        }

        */
        $cache_key = 'snowflake_'.$this->type.'_'.time();
        $sequence = $this->redis->incr($cache_key);
        if ($sequence==1)  $this->redis->expire($cache_key, 1);
        if ($sequence > $this->timestampMaxNum) {
            //睡眠到下一秒开始的时间
            list($microsecondsNow, $secondsNow) = explode(" ", microtime());
            $nextTime = 1000000 - (float)$microsecondsNow * 1000000;
            Cubox::log('snowflake_'.$this->type.'_'.time().' 内序列已用完 '.microtime(true));
            usleep($nextTime);
            return false;
        }else{
            return $sequence;
        }
        
    }

    /**
     * 解析id
     * @param [type] $id
     * @return array
     */

     public static function prase($id):array{

        $maxWorkerId = -1 ^ (-1 << self::WORKER_ID_BITS);

        //timestamp workerId sequence
        $sequence = $id & ( -1 ^ (-1 << self::SEQUENCE_BITS));
        $workerId = ($id >> self::SEQUENCE_BITS) & $maxWorkerId;
        $timestamp = ($id >> (self::SEQUENCE_BITS + self::WORKER_ID_BITS));


        return [
            'workerId'=>$workerId,
            'time'=>$timestamp + self::STARTTIME,
            'sequence'=>$sequence
        ];
    }
    
}